﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using Aeon.Emulator;
using System.ComponentModel;
using System.Collections.Specialized;
using Aeon.Emulator.Dos.VirtualFileSystem;

namespace Aeon.Configuration
{
    /// <summary>
    /// Collection of mounted drive configurations.
    /// </summary>
    public sealed class MountedDriveCollection : ICollection<MountedDrive>, INotifyPropertyChanged, INotifyCollectionChanged
    {
        #region Private Fields
        /// <summary>
        /// The internal list of drives.
        /// </summary>
        private readonly List<MountedDrive> drives = new List<MountedDrive>(26);
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the MountedDriveCollection class.
        /// </summary>
        public MountedDriveCollection()
        {
        }
        #endregion

        #region Public Events
        /// <summary>
        /// Occurs when a property value has changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
        /// <summary>
        /// Occurs when the collection has changed.
        /// </summary>
        public event NotifyCollectionChangedEventHandler CollectionChanged;
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets the number of drives in the collection.
        /// </summary>
        public int Count
        {
            get { return this.drives.Count; }
        }
        /// <summary>
        /// Gets a value indicating whether the collection is read-only.
        /// </summary>
        bool ICollection<MountedDrive>.IsReadOnly
        {
            get { return false; }
        }
        /// <summary>
        /// Gets the next available drive letter, or null if none are available.
        /// </summary>
        public DriveLetter? NextAvailableDriveLetter
        {
            get
            {
                return this.AvailableDriveLetters
                    .Select(d => (DriveLetter?)d)
                    .FirstOrDefault();
            }
        }
        /// <summary>
        /// Gets a collection of all available drive letters.
        /// </summary>
        public IEnumerable<DriveLetter> AvailableDriveLetters
        {
            get
            {
                for(int i = 0; i < 26; i++)
                {
                    if(this[new DriveLetter(i)] == null)
                        yield return new DriveLetter(i);
                }
            }
        }
        #endregion

        #region Public Indexers
        /// <summary>
        /// Gets the drive with a specified letter.
        /// </summary>
        /// <param name="driveLetter">Letter of drive to get.</param>
        /// <returns>Drive with specified letter if present; otherwise null.</returns>
        public MountedDrive this[DriveLetter driveLetter]
        {
            get { return this.FirstOrDefault(d => d.DriveLetter == driveLetter); }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Adds a new drive to the collection.
        /// </summary>
        public void Add()
        {
            Add(DriveType.None);
        }
        /// <summary>
        /// Adds a drive of a specific type to the collection.
        /// </summary>
        /// <param name="driveType">Type of drive to add.</param>
        public void Add(DriveType driveType)
        {
            var drive = new MountedDrive()
            {
                DriveLetter = this.NextAvailableDriveLetter.Value,
                DriveType = driveType
            };

            Add(drive);
        }
        /// <summary>
        /// Adds a drive to the collection.
        /// </summary>
        /// <param name="item">Drive to add.</param>
        public void Add(MountedDrive item)
        {
            if(item == null)
                throw new ArgumentNullException("item");
            if(this.drives.Count >= 26)
                throw new InvalidOperationException("Too many drives.");

            this.drives.Add(item);
            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, this.drives.Count - 1));
            this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
        }
        /// <summary>
        /// Adds a drive with a specific letter and type to the collection.
        /// </summary>
        /// <param name="driveLetter">Letter of the drive.</param>
        /// <param name="driveType">Type of the drive.</param>
        public void Add(DriveLetter driveLetter, DriveType driveType)
        {
            if(this[driveLetter] != null)
                throw new InvalidOperationException("Drive already defined.");

            var drive = new MountedDrive
            {
                DriveLetter = driveLetter,
                DriveType = driveType
            };

            this.drives.Add(drive);
            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, drive, this.drives.Count - 1));
            this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
        }
        /// <summary>
        /// Clears the collection of drives.
        /// </summary>
        public void Clear()
        {
            if(this.drives.Count > 0)
            {
                this.drives.Clear();
                this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
            }
        }
        /// <summary>
        /// Returns a value indicating whether a drive is contained in the collection.
        /// </summary>
        /// <param name="item">Drive to search for.</param>
        /// <returns>True if drive was found; otherwise false.</returns>
        public bool Contains(MountedDrive item)
        {
            return this.drives.Contains(item);
        }
        /// <summary>
        /// Copies the collection to an array.
        /// </summary>
        /// <param name="array">Array into which collection items are copied.</param>
        /// <param name="arrayIndex">Index in target array to start copying.</param>
        public void CopyTo(MountedDrive[] array, int arrayIndex)
        {
            this.ToArray().CopyTo(array, arrayIndex);
        }
        /// <summary>
        /// Removes a drive from the collection.
        /// </summary>
        /// <param name="item">Drive to remove.</param>
        /// <returns>True if drive was removed; false if it was not found.</returns>
        public bool Remove(MountedDrive item)
        {
            int index = this.drives.IndexOf(item);
            if(index >= 0)
            {
                this.drives.Remove(item);
                this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
                this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
                return true;
            }
            else
                return false;
        }
        /// <summary>
        /// Returns an enumerator for the collection.
        /// </summary>
        /// <returns>Enumerator for the collection.</returns>
        public IEnumerator<MountedDrive> GetEnumerator()
        {
            return (from d in this.drives
                    orderby d.DriveLetter
                    select d).GetEnumerator();
        }
        /// <summary>
        /// Returns an enumerator for the collection.
        /// </summary>
        /// <returns>Enumerator for the collection.</returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Raises the PropertyChanged event.
        /// </summary>
        /// <param name="e">Information about the event.</param>
        private void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            var handler = this.PropertyChanged;
            if(handler != null)
                handler(this, e);
        }
        /// <summary>
        /// Raises the CollectionChanged event.
        /// </summary>
        /// <param name="e">Information about the event.</param>
        private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            var handler = this.CollectionChanged;
            if(handler != null)
                handler(this, e);
        }
        #endregion
    }
}
